feat: AI budgeting chat with human-in-the-loop envelope assignment#16
Merged
Conversation
A chat assistant on the budget page reads live budget state via tools and PROPOSES envelope assignments/transfers for the user to approve. The chat route never writes the journal; money moves only via the existing assign/transfer endpoints after explicit approval (guarded by a zero-write test). - server/utils/anthropic.ts: shared Anthropic SDK client (Opus 4.8, adaptive thinking). Key resolves env override -> in-app stored key; client rebuilds on key change (no restart). Typed billing/401/429 error messages. - server/utils/aiConfig.ts + /api/ai/config (GET/POST/DELETE): in-app API key config persisted to gitignored config/ai-config.json; key never logged or returned in full (masked last-4). Settings page gains an AI Assistant card. - server/utils/aiTools.ts + server/ai/budgetInstructions.ts: read tools (get_budget/get_transactions) + proposed-action tools; cached system prompt. - server/api/ai/chat.post.ts: stateless HITL tool loop (manual, 8-iter cap) -- surfaces proposals, never executes them. - composables/useAiChat.ts + components/AiChatPanel.vue: chat UI with approve/reject cards, data-egress notice, not-configured empty state. - server/utils/budgetReport.ts: getBudgetReport extracted from budget.get.ts so the route and the get_budget tool share one source (no duplicated accounting). Verified: 389 vitest tests + nuxi typecheck clean; runtime-probed the config endpoints and the HITL no-write invariant. Spec in .kiro/specs/ai-budgeting-chat/. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #8
A chat assistant on the budget page that reads live budget state through tools and proposes envelope assignments/transfers for the user to approve. This is the foundation feature that establishes the shared Anthropic plumbing and the propose → approve → commit spine the CSV-import feature (#9) will reuse.
Safety invariant (load-bearing)
The chat route never writes the journal. Read tools run server-side; the proposed-action tools (
assign_to_envelope,transfer_between_envelopes) are surfaced for approval, never executed. Money moves only when the client calls the existingPOST /api/budget/assign|transferendpoints after explicit user approval. Guarded by a test asserting the journal writer is called 0×.What's included
server/utils/anthropic.ts— shared Anthropic SDK client (claude-opus-4-8, adaptive thinking, effort medium). Key resolves env override → in-app stored key; the client rebuilds when the key changes (no restart). Typed billing/401/429 error messages.server/utils/aiConfig.ts+GET/POST/DELETE /api/ai/config— in-app API-key configuration persisted to gitignoredconfig/ai-config.json. The key is never logged and never returned in full (masked last-4). Settings page gains an "AI Assistant" card.server/utils/aiTools.ts+server/ai/budgetInstructions.ts— read tools (get_budget/get_transactions) + proposed-action tools; cached system prompt.server/api/ai/chat.post.ts— stateless human-in-the-loop tool loop (manual, 8-iteration cap); refusal/missing-key/error handling.composables/useAiChat.ts+components/AiChatPanel.vue— chat UI with approve/reject cards, persistent data-egress notice, and a not-configured empty state linking to Settings.server/utils/budgetReport.ts—getBudgetReportextracted frombudget.get.tsso the route and theget_budgettool share one source (no duplicated accounting logic).Notable decision
Issue #8 framed the key as env-only ("no stored secrets"). Per discussion, this PR adds an in-app key config persisted to gitignored
config/ai-config.json(mirrors theconfig/active-journal.jsonprecedent; acceptable for a single-user local app). Env var still takes precedence. Recorded in.kiro/specs/ai-budgeting-chat/design.md(Decision: in-app key config) and requirement R7.Verification
nuxi typecheckclean./api/ai/configverbs (save takes effect with no restart) and confirmed the HITL no-write invariant.Data egress
Chat messages + budget data are sent to the Anthropic API (the one external data flow). The chat panel shows a persistent notice; no secrets are stored beyond the gitignored API key.
Spec:
.kiro/specs/ai-budgeting-chat/(design → requirements → tasks).🤖 Generated with Claude Code